package ee.parameterConverter.impl;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ee.metadataUtils.ObjectFactory;
import ee.metadataUtils.PropertyMetadataRegistry;
import ee.parameterConverter.ComplexConverter;
import ee.parameterConverter.ConversionFailedException;
import ee.parameterConverter.Converter;
import ee.parameterConverter.ConverterResolver;
import ee.parameterConverter.EmptyValueException;
import ee.parameterConverter.ListConverter;
import ee.parameterConverter.MapConverter;
import ee.parameterConverter.ParameterConverter;
import ee.parameterConverter.SimpleConverter;
import ee.parameterConverter.TypeKind;
import ee.parameterConverter.TypeKindResolver;
import ee.parameterConverter.ValidationResultCollector;

public class ParameterConverterImpl implements ParameterConverter, SimpleConverter {

	private TypeKindResolver _typeKindResolver;
	private ConverterResolver _converterResolver;
	private SimpleConverter _simpleConverter;
	private ListConverter _listConverter;
	private MapConverter _mapConverter;
	private ComplexConverter _complexConverter;
	private ValidationResultCollector _validationResultCollector;
	
	public ParameterConverterImpl(TypeKindResolver typeKindResolver,
			ConverterResolver converterResolver,
			ObjectFactory objectFactory, PropertyMetadataRegistry metadataRegistry,
			ValidationResultCollector validationResultCollector) {

		_typeKindResolver = typeKindResolver;
		_converterResolver = converterResolver;
		_validationResultCollector = validationResultCollector;
		
		_simpleConverter = getSimpleConverter();
		_listConverter = getListConverter();
		_mapConverter = getMapConverter();
		_complexConverter = getComplexConverter(objectFactory, metadataRegistry);
	}

	protected ComplexConverter getComplexConverter(ObjectFactory objectFactory,
			PropertyMetadataRegistry metadataRegistry) {
		return new ComplexConverterImpl(this, _converterResolver, _validationResultCollector, objectFactory, metadataRegistry);
	}

	protected MapConverter getMapConverter() {
		return new MapConverterImpl(this, _converterResolver, _validationResultCollector);
	}

	protected ListConverter getListConverter() {
		return new ListConverterImpl(this, _converterResolver, _validationResultCollector);
	}

	protected SimpleConverter getSimpleConverter() {
		return new SimpleConverterImpl(_converterResolver);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T convert(Map<String, String[]> parameters, Class<T> type)
			throws ConversionFailedException, EmptyValueException {
		return (T) convert(parameters, (Type) type);
	}

	@Override
	public Object convert(Map<String, String[]> parameters, Type type)
			throws ConversionFailedException, EmptyValueException {
		return convert(parameters, type, null);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T convert(Map<String, String[]> parameters, Class<T> type,
			String key) throws ConversionFailedException, EmptyValueException {
		return (T) convert(parameters, (Type) type, key);
	}
	
	@Override
	public Object convert(Map<String, String[]> parameters, Type type, String key)
			throws ConversionFailedException, EmptyValueException {

		// First check if a converter is present for these type of conversions
		Converter<Map<String, String[]>, ?> converter = _converterResolver.getParameterConverter(type);

		if (converter != null) {
			return converter.convert(parameters);
		}

		// Determine the kind of type we are dealing with
		TypeKind typeKind = _typeKindResolver.resolve(type);

		if (key == null || key.length() == 0) {
			if (typeKind.equals(TypeKind.Complex) || typeKind.equals(TypeKind.Map)) {
				// we can proceed with conversion
				if (typeKind.equals(TypeKind.Complex)) {
					return _complexConverter.convert(parameters, type);
				} else {
					return _mapConverter.convert(parameters, type);
				}
			} else {
				throw new ConversionFailedException( 
						"Could not convert the given parameters to type '"
								+ type
								+ "', no key was provided and the type is a SIMPLE or LIST TypeKind.", parameters);
			}
		} else {
			List<String> parameterPath = new ArrayList<String>();
			parameterPath.add(key);
			
			try
			{
				switch (typeKind) {
					case Complex:
						return _complexConverter.convert(parameters, type, key);
					case Map:
						return _mapConverter.convert(parameters, type, key);
					case List:
						return _listConverter.convert(parameters.get(key), type);
					case Simple:
							return _simpleConverter.convert(parameters.get(key), type);
					default:
						throw new ConversionFailedException("Unknown type kind: " + typeKind, parameters);
				}
			} catch (ConversionFailedException e)
			{
				_validationResultCollector.processException(parameterPath, e);
				return null;
			}
		}
	}
	
	protected Object convert(Map<String, Object> normalizedParameters, Type type, List<String> parameterPath) throws ConversionFailedException, EmptyValueException
	{
		TypeKind typeKind = _typeKindResolver.resolve(type);
		
		switch (typeKind)
		{
			case Complex:
				return _complexConverter.convert(normalizedParameters, type, parameterPath);
			case Map:
				return _mapConverter.convert(normalizedParameters, type, parameterPath);
			case List:
				return _listConverter.convert(normalizedParameters, type, parameterPath);
			default:
				throw new ConversionFailedException("Could not convert parameters to " + type);
		}
	}
	
	@Override
	public Object convert(String[] value, Type type) throws ConversionFailedException,
			EmptyValueException {
		return _simpleConverter.convert(value, type);
	}

	@Override
	public Object convert(String value, Type type) throws ConversionFailedException,
	EmptyValueException {
		return _simpleConverter.convert(value, type);
	}
	
	protected Map<String, Object> normalizeParameters(Map<String, String[]> parameters, String parameterKey) {

		Map<String, Object> normalized = new HashMap<String, Object>();

		boolean hasParameterKey = parameterKey != null;
		
		for (String key : parameters.keySet()) {
			// check if we have a complex key
			String[] keyArray = key.split("\\.");

			int keyArrayLength = keyArray.length;
			if (keyArrayLength > 1) {
				// loop through the key parts and add them
				Map<String, Object> parent = normalized;

				for (int i = 0; i < keyArrayLength; i++) {
					String keyPart = keyArray[i];

					if (hasParameterKey && i < 1 && !parameterKey.equals(keyPart))
					{
						//skip this key
						break;
					}
					
					if (i < keyArrayLength - 1) {
						// create a sub map if needed (newParent is used as a variable in order to be able to add an
						if (parent.containsKey(keyPart)) {
							@SuppressWarnings("unchecked")
							Map<String, Object> newParent = (Map<String, Object>) parent
									.get(keyPart);
							parent = newParent;
						} else {
							Map<String, Object> newParent = new HashMap<String, Object>();
							parent.put(keyPart, newParent);
							parent = newParent;
						}
					} else {
						// we are at the last key part, add the value
						parent.put(keyPart, parameters.get(key));
					}
				}
			} else {
				if (!hasParameterKey || parameterKey.equals(key))
				{
					// a simple key, just add it to the normalized map
					normalized.put(key, parameters.get(key));
				}
			}
		}

		return normalized;
	}

	@Override
	public ValidationResultCollector getValidationResultCollector() {
		return _validationResultCollector;
	}
}
